package com.xjm.i1.core.generator;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.xjm.i1.constant.ColorEnum;
import com.xjm.i1.constant.MinDensityEnum;
import com.xjm.i1.convert.ExcelContrastConvert;
import com.xjm.i1.core.struct.node.load.I1AreaCollections;
import com.xjm.i1.core.struct.node.load.I1DataCollections;
import com.xjm.i1.core.struct.node.load.I1DeviceCollections;
import com.xjm.i1.core.struct.node.load.I1PageCollections;
import com.xjm.i1.core.struct.node.write.*;
import com.xjm.i1.entity.ColorContrastArea;
import com.xjm.i1.entity.ColorDiffDensityArea;
import com.xjm.i1.entity.ColorGrayArea;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Workbook;

import java.io.File;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Excel生成导出
 * */
@Slf4j
public class ExcelGenerator {

    // Excel接受后缀
    public static final String SUPPORT_SUFFIX = "xlsx";

    // 默认导出模板
//    public static final String DEFAULT_TEMPLATE_PATH = ResourceUtil.getResource("template/exportTemplate.xlsx").getPath();
    public static final ClassPathResource DEFAULT_TEMPLATE_PATH = new ClassPathResource("template/exportTemplate.xlsx");

    // 对应：模板sheet名称 -> 导出数据
    private final Map<String, List> generateSheets = new LinkedHashMap<>();

    // Excel导出的数据
    private ExcelWriteData excelWriteData;

    public ExcelGenerator() {
        excelWriteData = new ExcelWriteData();
        excelWriteData.setExcelContrasts(new LinkedList<>());
        excelWriteData.setExcelDiffDensities10(new LinkedList<>());
        excelWriteData.setExcelDiffDensities30(new LinkedList<>());
        excelWriteData.setExcelDiffDensities60(new LinkedList<>());
        excelWriteData.setExcelDiffDensities100(new LinkedList<>());
        excelWriteData.setExcelGrays(new LinkedList<>());
        generateSheets.put("色差与密度-100%", excelWriteData.getExcelDiffDensities100());
        generateSheets.put("色差与密度-60%", excelWriteData.getExcelDiffDensities60());
        generateSheets.put("色差与密度-30%", excelWriteData.getExcelDiffDensities30());
        generateSheets.put("色差与密度-10%", excelWriteData.getExcelDiffDensities10());
        generateSheets.put("阶调", excelWriteData.getExcelContrasts());
        generateSheets.put("灰度偏差", excelWriteData.getExcelGrays());
    }

    /**
     * 生成excel并导出
     * @param i1Data 加载数据的顶层结构
     * @param templatePath 导出模板目录
     * */
    public void generateExcelAndOutPut(I1DataCollections i1Data, InputStream templatePath) {

        log.info("========== 开始导出 ==========");
        Assert.isTrue(templatePath!=null, "检测到外置设置Excel导出模板输入流为空");
        String outFilePath = i1Data.getOutputPath() + File.separator + i1Data.getGenerateName() + "." + SUPPORT_SUFFIX;
        ExcelWriter writer = EasyExcel.write(outFilePath).withTemplate(templatePath).build();
        Workbook workbook = writer.writeContext().writeWorkbookHolder().getWorkbook();
        workbook.setForceFormulaRecalculation(true);
        workbook.getCreationHelper().createFormulaEvaluator().evaluateAll();
        try {
            for (Map.Entry<String, List> sheet: generateSheets.entrySet()) {
                log.info(String.format("正在加载导出sheet:[%s]，数据共[%d]行", sheet.getKey(), sheet.getValue().size()));
                WriteSheet writeSheet = EasyExcel.writerSheet(sheet.getKey()).build();
                FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();
                List groupByColor= groupExcelDataByColor(sheet.getValue());
                // 分组后的数据还需按
                ExcelDiffDensityExtend excelDiffDensityExtend = formulaDiffDensityExtend(groupByColor);

                writer.fill(groupByColor, fillConfig, writeSheet);
                if (!ObjectUtil.isNull(excelDiffDensityExtend)) {
                    writer.fill(excelDiffDensityExtend, fillConfig, writeSheet);
                }
            }
        } catch (Exception ex) {
            System.out.println(ex);
            log.error(String.format("写出excel文件时出现问题[%s]", ex.getMessage()), ex);
        } finally {
            writer.finish();
            log.info("进入最后导出操作 >> 正在关闭Excel文件");
        }
        log.info("========== 导出完成 ==========");
    }

    /**
     * 计算颜色色差密度公式
     * @param collections 写入excel的数据集合
     * @return ExcelDiffDensityExtend 计算公式的填充结果
     * */
    private ExcelDiffDensityExtend formulaDiffDensityExtend(List<? extends AbstractExcelCollection> collections) {
        // 灰度或阶调跳过
        Class clazz = collections.get(0).getClass();
        if (clazz.isAssignableFrom(ExcelGray.class) || clazz.isAssignableFrom(ExcelContrast.class)) {
            return null;
        }
        ExcelDiffDensityExtend res = new ExcelDiffDensityExtend();
        for (Map.Entry<String, ? extends List<? extends AbstractExcelCollection>> group: doGroupByColor(collections).entrySet()) {
            ColorEnum colorEnumByVal = ColorEnum.getColorEnumByVal(group.getKey());
            List<? extends AbstractExcelCollection> value = group.getValue();
            switch (colorEnumByVal){
                case CYAN:
                    res.setCyanParams((List<ExcelDiffDensity>) value);
                    break;
                case BULE:
                    res.setBlueParams((List<ExcelDiffDensity>) value);
                    break;
                case GREEN:
                    res.setGreenParams((List<ExcelDiffDensity>) value);
                    break;
                case RED:
                    res.setRedParams((List<ExcelDiffDensity>) value);
                    break;
                case YELLOW:
                    res.setYellowParams((List<ExcelDiffDensity>) value);
                    break;
                case MAGENTA:
                    res.setMagentaParams((List<ExcelDiffDensity>) value);
                    break;
                case BLACK:
                    res.setBlackParams((List<ExcelDiffDensity>) value);
                    break;
            }
        }
        return res;
    }

    /**
     * 将excel数据按颜色进行分类
     * @param collections Excel写入的数据
     * */
    private List<? extends AbstractExcelCollection> groupExcelDataByColor(List<? extends AbstractExcelCollection> collections) {
        // 灰度偏差不需求颜色分类
        Class clazz = collections.get(0).getClass();
        if (clazz.isAssignableFrom(ExcelGray.class)) {
            return collections;
        }
        List<AbstractExcelCollection> res = new LinkedList<>();
        Map<String, ? extends List<? extends AbstractExcelCollection>> sortMap = doGroupByColor(collections);

        if (clazz.isAssignableFrom(ExcelContrast.class)) {
            // 根据CMYK G排序
            for (String key : ColorEnum.getContrastStringOrdered()) {
                List<? extends AbstractExcelCollection> list = sortMap.get(key);
                if (!CollectionUtil.isEmpty(list)) {
                    boolean addAllRes = res.addAll(list);
                    Assert.isTrue(addAllRes, String.format("[%s]类Excel数据集合进行转换时出现错误!", clazz.getSimpleName()));
                }
            }
        } else {
            // 根据CMYK RGB排序
            for (String key : ColorEnum.getDensityStringOrdered()) {
                List<? extends AbstractExcelCollection> list = sortMap.get(key);
                if (!CollectionUtil.isEmpty(list)) {
                    boolean addAllRes = res.addAll(list);
                    Assert.isTrue(addAllRes, String.format("[%s]类Excel数据集合进行转换时出现错误!", clazz.getSimpleName()));
                }
            }
        }
        return res;
    }

    /**
     * 根据颜色分组，并排序
     * */
    private Map<String,? extends List<? extends AbstractExcelCollection>> doGroupByColor(List<? extends AbstractExcelCollection> collections) {
        return collections.stream().collect(Collectors.groupingBy(AbstractExcelCollection::group));
    }

    /**
     * 将加载数据转换为导出数据,赋值至 {@linkplain ExcelGenerator#excelWriteData}
     * @param i1Data 加载数据的顶层结构
     * */
    public void convertExcelData(I1DataCollections i1Data) {

        for (Map.Entry<String, I1DeviceCollections> dataEntry : i1Data.entrySet()) {
            String deviceId = dataEntry.getKey();
            I1DeviceCollections i1Device = dataEntry.getValue();

            for (Map.Entry<String, I1PageCollections> devEntry: i1Device.entrySet()) {
                I1PageCollections i1Page = devEntry.getValue();
                String pageId = devEntry.getKey();
                String env = i1Page.getEnv();
                List<I1AreaCollections> areaCollections = i1Page.getAreaCollections();
                // 搜集一个纸张中所有区域的色差与密度数据
                List<ColorDiffDensityArea> diffDensityAllAreaList = new LinkedList<>();
                for (I1AreaCollections i1Area : areaCollections) {
                    switch (i1Area.getAreaType()) {
                        case COLOR_CONTRAS_GRAY:
                            addExcelContrasts(i1Area.getColorContrastAreas(), deviceId, env, pageId);
                            addExcelGrays(i1Area.getColorGrayAreas(), deviceId, env, pageId);
                            break;
                        case COLOR_DIFF_DENSITY:
                            diffDensityAllAreaList.addAll(i1Area.getColorDiffDensityArea());
                            break;
                    }
                }
                addExcelDiffDensitys(diffDensityAllAreaList, deviceId, env, pageId);
            }
        }
    }

    /**
     * 处理色差与密度数据转换，属于{@linkplain ExcelGenerator#convertExcelData(I1DataCollections)} 一部分
     * @param colorDiffDensityAreas 一页中所有区域的色差与密度数据List
     * @param deviceId 所属设备编号
     * @param env 设备环境
     * @param pageId 所属页面编号
     * */
    private void addExcelDiffDensitys(List<ColorDiffDensityArea> colorDiffDensityAreas, String deviceId, String env, String pageId) {
        List<ExcelDiffDensity> collections10 = this.excelWriteData.getExcelDiffDensities10();
        List<ExcelDiffDensity> collections30 = this.excelWriteData.getExcelDiffDensities30();
        List<ExcelDiffDensity> collections60 = this.excelWriteData.getExcelDiffDensities60();
        List<ExcelDiffDensity> collections100 = this.excelWriteData.getExcelDiffDensities100();
        Map<String, ExcelDiffDensity> excelDiffDensityMap = new HashMap();

        // 根据百分比-颜色进行分类
        for (ColorDiffDensityArea colorDiffDensityArea : colorDiffDensityAreas) {
            // key-> 百分比+'-'+颜色
            ColorEnum colorSign = colorDiffDensityArea.getColorSign();
            MinDensityEnum densitySign = colorDiffDensityArea.getDensitySign();
            String key = densitySign.name() + "-" + colorSign.getVal();
            ExcelDiffDensity excelDiffDensity = null;
            if (excelDiffDensityMap.containsKey(key)) {
                excelDiffDensity = excelDiffDensityMap.get(key);
            } else {
                excelDiffDensity = new ExcelDiffDensity(deviceId, env, pageId, colorSign);
                // excelDiffDensity.setColor(color);
                // excelDiffDensity.setDensitySign(densitySign);
            }

            switch (colorDiffDensityArea.getAreaSign()) {
                case UPLEFT:
                    excelDiffDensity.setValueULL(colorDiffDensityArea.getValueL());
                    excelDiffDensity.setValueULA(colorDiffDensityArea.getValueA());
                    excelDiffDensity.setValueULB(colorDiffDensityArea.getValueB());
                    excelDiffDensity.setValueULDensity(colorDiffDensityArea.getValueDensity());
                    break;
                case DOWNLEFT:
                    excelDiffDensity.setValueDLL(colorDiffDensityArea.getValueL());
                    excelDiffDensity.setValueDLA(colorDiffDensityArea.getValueA());
                    excelDiffDensity.setValueDLB(colorDiffDensityArea.getValueB());
                    excelDiffDensity.setValueDLDensity(colorDiffDensityArea.getValueDensity());
                    break;
                case UPRIGHT:
                    excelDiffDensity.setValueURL(colorDiffDensityArea.getValueL());
                    excelDiffDensity.setValueURA(colorDiffDensityArea.getValueA());
                    excelDiffDensity.setValueURB(colorDiffDensityArea.getValueB());
                    excelDiffDensity.setValueURDensity(colorDiffDensityArea.getValueDensity());
                    break;
                case DOWNRIGHT:
                    excelDiffDensity.setValueDRL(colorDiffDensityArea.getValueL());
                    excelDiffDensity.setValueDRA(colorDiffDensityArea.getValueA());
                    excelDiffDensity.setValueDRB(colorDiffDensityArea.getValueB());
                    excelDiffDensity.setValueDRDensity(colorDiffDensityArea.getValueDensity());
                    break;
                case MIDDLE:
                    excelDiffDensity.setValueML(colorDiffDensityArea.getValueL());
                    excelDiffDensity.setValueMA(colorDiffDensityArea.getValueA());
                    excelDiffDensity.setValueMB(colorDiffDensityArea.getValueB());
                    excelDiffDensity.setValueMDensity(colorDiffDensityArea.getValueDensity());
                    break;
            }
            excelDiffDensityMap.put(key, excelDiffDensity);
        }

        // 将分类的数据导入Excel写入数据中
        for (Map.Entry<String, ExcelDiffDensity> entry : excelDiffDensityMap.entrySet()) {

            // 根据百分比将数据存入ExcelWriteData的各个ExcelDiffDensity中
            String key = entry.getKey();
            ExcelDiffDensity excelDiffDensity = entry.getValue();
            String densityVal = key.substring(0, key.indexOf("-"));

            // 计算单行公式
            excelDiffDensity.setFormulaDensityAVG(excelDiffDensity.calculateDensityAVG());
            excelDiffDensity.setFormulaDensitySingle(excelDiffDensity.calculateDensitySingle());
            excelDiffDensity.setFormulaDensitySinglePercent(excelDiffDensity.calculateDensitySinglePercent());
            switch (Enum.valueOf(MinDensityEnum.class, densityVal)) {
                case TEN_PERCENT:
                    collections10.add(excelDiffDensity);
                    break;
                case THIRTY_PERCENT:
                    collections30.add(excelDiffDensity);
                    break;
                case SIXTY_PERCENT:
                    collections60.add(excelDiffDensity);
                    break;
                case HUNDRED_PERCENT:
                    collections100.add(excelDiffDensity);
                    break;
            }
        }
    }

    /**
     * 处理阶调数据转换，属于{@linkplain ExcelGenerator#convertExcelData(I1DataCollections)} 一部分
     * @param colorContrastAreas 一页中所有阶调数据List
     * @param deviceId 所属设备编号
     * @param env 设备环境
     * @param pageId 所属页面编号
     * */
    private void addExcelContrasts(List<ColorContrastArea> colorContrastAreas, String deviceId, String env, String pageId) {
        List<ExcelContrast> collections = this.excelWriteData.getExcelContrasts();
        for (ColorContrastArea colorContrastArea : colorContrastAreas) {
            ExcelContrast excelContrast = ExcelContrastConvert.INSTANCE.convert(colorContrastArea);
            excelContrast.setColor(colorContrastArea.getColorSign().getVal());
            excelContrast.setDeviceId(deviceId);
            excelContrast.setEnv(env);
            excelContrast.setPageId(pageId);
            collections.add(excelContrast);
        }
    }

    /**
     * 处理灰度偏差数据转换，属于{@linkplain ExcelGenerator#convertExcelData(I1DataCollections)} 一部分
     * @param colorGrayAreas 一页中所有灰度偏差数据List
     * @param deviceId 所属设备编号
     * @param env 设备环境
     * @param pageId 所属页面编号
     * */
    private void addExcelGrays(List<ColorGrayArea> colorGrayAreas, String deviceId, String env, String pageId) {
        List<ExcelGray> collections = this.excelWriteData.getExcelGrays();
        ExcelGray excelGray = new ExcelGray(deviceId, env, pageId);
        for (ColorGrayArea colorGrayArea : colorGrayAreas) {
            switch (colorGrayArea.getGraySign()) {
                case TWENTY_FIVE_PERCENT:
                    excelGray.setValue25A(colorGrayArea.getValueA());
                    excelGray.setValue25L(colorGrayArea.getValueL());
                    excelGray.setValue25B(colorGrayArea.getValueB());
                    break;
                case FIFTY_PERCENT:
                    excelGray.setValue50A(colorGrayArea.getValueA());
                    excelGray.setValue50L(colorGrayArea.getValueL());
                    excelGray.setValue50B(colorGrayArea.getValueB());
                    break;
                case SEVENTY_FIVE_PERCENT:
                    excelGray.setValue75A(colorGrayArea.getValueA());
                    excelGray.setValue75L(colorGrayArea.getValueL());
                    excelGray.setValue75B(colorGrayArea.getValueB());
                    break;
                case HUNDRED_PERCENT:
                    excelGray.setValue100A(colorGrayArea.getValueA());
                    excelGray.setValue100L(colorGrayArea.getValueL());
                    excelGray.setValue100B(colorGrayArea.getValueB());
                    break;
            }
        }
        excelGray.setFormula25AB(excelGray.calculate25AB());
        excelGray.setFormula50AB(excelGray.calculate50AB());
        excelGray.setFormula75AB(excelGray.calculate75AB());
        excelGray.setFormula100AB(excelGray.calculate100AB());
        excelGray.setFormulaABAVG(excelGray.calculateABAVG());
        collections.add(excelGray);
    }
}
